package  
{
	// SmartFox Imports
	import as3isolib.geom.Pt;
	import com.smartfoxserver.v2.entities.managers.SFSUserManager;
	import com.smartfoxserver.v2.entities.match.RoomProperties;
	import com.smartfoxserver.v2.entities.variables.RoomVariable;
	import com.smartfoxserver.v2.entities.variables.UserVariable;
	import com.smartfoxserver.v2.SmartFox;
	import com.smartfoxserver.v2.core.SFSEvent;
	import com.smartfoxserver.v2.controllers.ExtensionController;
	import com.smartfoxserver.v2.entities.variables.SFSUserVariable;
	import com.smartfoxserver.v2.entities.variables.SFSRoomVariable;
	import com.smartfoxserver.v2.entities.*;
	import com.smartfoxserver.v2.entities.data.*;
	import com.smartfoxserver.v2.requests.*;
	import com.smartfoxserver.v2.entities.Room;
	
	import UI.*;
	
	/**
	 * ...
	 * @author Bryan Keiren & Sander van Katwijk
	 */
	public class MessageHandler 
	{
		static private var m_Singleton:MessageHandler = null;
		static public function Get():MessageHandler		{	return m_Singleton;		}
		
		// Current message ID.
		static private var m_MessageID:int = 0;
		
		// SmartFox connection object.
		public var m_SFS:SmartFox;
		
		public function MessageHandler() 
		{
		}
		public function init():void
		{
			m_Singleton = this;
			
			initSmartFoxServerObject();
		}
		
		private function initSmartFoxServerObject():void
		{
			// Create an instance of the SmartFox class.
			m_SFS = new SmartFox( true ); // True is for debugging information on.
										  // TODO: Turn off for production use.
			// Add SFS event listeners.
			m_SFS.addEventListener(SFSEvent.CONNECTION, 			onConnection);
			m_SFS.addEventListener(SFSEvent.LOGIN, 					onLogin);
			m_SFS.addEventListener(SFSEvent.LOGIN_ERROR, 			onLoginError);
			m_SFS.addEventListener(SFSEvent.ROOM_JOIN, 				onRoomJoin);
			m_SFS.addEventListener(SFSEvent.ROOM_JOIN_ERROR, 		onRoomJoinError);
			m_SFS.addEventListener(SFSEvent.CONNECTION_LOST, 		onConnectionLost);
			m_SFS.addEventListener(SFSEvent.CONFIG_LOAD_SUCCESS, 	onConfigLoadSuccess);
			m_SFS.addEventListener(SFSEvent.CONFIG_LOAD_FAILURE, 	onConfigLoadFailure);
			m_SFS.addEventListener(SFSEvent.EXTENSION_RESPONSE, 	onExtensionResponse);
			m_SFS.addEventListener(SFSEvent.USER_ENTER_ROOM, 		onUserEnterRoom);
			m_SFS.addEventListener(SFSEvent.USER_EXIT_ROOM, 		onUserExitRoom);
			m_SFS.addEventListener(SFSEvent.USER_VARIABLES_UPDATE, 	onUserVariableUpdate);
			m_SFS.addEventListener(SFSEvent.ROOM_ADD,				onRoomAdded);
			m_SFS.addEventListener(SFSEvent.ROOM_CREATION_ERROR, 	onRoomCreationError);
			m_SFS.addEventListener(SFSEvent.ROOM_REMOVE, 			onRoomRemove);
			m_SFS.addEventListener(SFSEvent.ROOM_VARIABLES_UPDATE, 	onRoomVariablesUpdate);
			
			// Echo the first trace messages to the console.
			Engine.printf("SmartFox API: " + m_SFS.version);
			
			// Load the SmartFoxServer config file (it looks for sfs-config.xml).
			m_SFS.loadConfig();
		}
		
		
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// Message Receiving, handles the messages or sends them to the correct classes.
		
		private function onConfigLoadSuccess(evt:SFSEvent):void
		{
			Engine.printf("Config load success!");
			Engine.printf("Server settings: " + m_SFS.config.host + ":" + m_SFS.config.port);
		}
		private function onConfigLoadFailure(evt:SFSEvent):void
		{
			Engine.printf("Config load failure!");
		}
		private function onConnection(evt:SFSEvent):void
		{
			if (evt.params.success)
			{
				Engine.printf("Connection Success, ready to send log in request.");
			}
			else
			{
				Engine.printf("Connection Failure: " + evt.params.errorMessage);
			}
		}
		private function onConnectionLost(evt:SFSEvent):void
		{
			Engine.printf("Connection was lost. Reason: " + evt.params.reason);
			Engine.Get().SwitchAppState(AppState.MENU);
		}
		private function onLogin(evt:SFSEvent):void
		{
			Engine.printf("Logged in as " + evt.params.user.name + ", joining room: " + Globals.Lobby_Room_Name);
			Engine.Get().SwitchAppState(AppState.LOBBY);
			sendJoinRoomRequest(Globals.Lobby_Room_Name);
			
			LocalPlayer.Get().setName(MessageHandler.Get().m_SFS.mySelf.name);
			
			// Set some user vars to make sure they are available.
			var UserVars:Array = [];
			UserVars.push( new SFSUserVariable("pos_x", 0) );
			UserVars.push( new SFSUserVariable("pos_y", 0) );
			UserVars.push( new SFSUserVariable("teamid", 0) );
			UserVars.push( new SFSUserVariable("target_x", 0) );
			UserVars.push( new SFSUserVariable("target_y", 0) );
			UserVars.push( new SFSUserVariable("score", 0) );
			MessageHandler.Get().sendSetUserVariablesRequest(UserVars);
			// -- 
		}
		private function onLoginError(evt:SFSEvent):void
		{
			Engine.printf("Error while logging in: " + evt.params.errorMessage);
			
			// TODO: An on-screen error message to the player.
		}
		private function onRoomJoin(evt:SFSEvent):void
		{
			var l_room:Room = evt.params.room;
			//var l_user:User = evt.params.user;
			
			Globals.Current_Game_Room = evt.params.room;
			
			// If the game lobby was just joined...
			if (l_room.name == Globals.Lobby_Room_Name)
			{
				// Request a list of available games.
				//sendExtensionRequest("gameslist", new SFSObject());
				
				// Obtain the current game session from the room variables.
				var roomVars:Array = l_room.getVariables();
				
				Engine.Get().updateSessionList();
			}
			// Else if a session room was just joined.
			else if (Util.strStartsWith(l_room.name, "Session"))
			{
				Globals.Current_Game_Room = l_room;
				Engine.Get().SwitchAppState(AppState.GAME);
				
				var LocalPlayerID:int = l_room.userCount - 1;
				var LocalPlayerTeamID:int = (LocalPlayerID + 1);
				var LocalPlayerPos:Pt = Globals.TeamStartPositions[LocalPlayerID];
				
				var UserVars:Array = [];
				UserVars.push( new SFSUserVariable("pos_x", LocalPlayerPos.x) );
				UserVars.push( new SFSUserVariable("pos_y", LocalPlayerPos.y) );
				UserVars.push( new SFSUserVariable("teamid", LocalPlayerTeamID) );
				UserVars.push( new SFSUserVariable("target_x", LocalPlayerPos.x) );
				UserVars.push( new SFSUserVariable("target_y", LocalPlayerPos.y) );
				UserVars.push( new SFSUserVariable("score", 0) );
				MessageHandler.Get().sendSetUserVariablesRequest(UserVars);
				
				// Empty the list of players and teams.
				Player.destroyAllExternalPlayers();
				Team.destroyAllExternalTeams();
				Flag.destroyAllFlags();
				
				// If the server takes a long time to respond, the application will freeze on this part. Be aware of this!
				// This is here because we want to set the variables but we might not have access to them directly.
				
				while (m_SFS.mySelf.getVariable("score") == null) { }
				
				LocalPlayer.Get().setPosSynched(new Vector2( LocalPlayerPos.x, LocalPlayerPos.y ));
				LocalPlayer.Get().setTargetPos(new Vector2( LocalPlayerPos.x, LocalPlayerPos.y ));
				LocalPlayer.Get().getTeam().setID( LocalPlayerTeamID );
				LocalPlayer.Get().resetPlayTimer();
				
				// Loop over the users which are already in the room.
				for (var i:int = 0; i < l_room.userList.length; ++i)
				{
					var l_user:User = l_room.userList[i];					
					
					// Creat a new player.
					if (l_user.name != m_SFS.mySelf.name)
					{
						var newPlayer:Player = new Player(l_user.name, new Vector2(l_user.getVariable("pos_x").getIntValue(), l_user.getVariable("pos_y").getIntValue()), new Team(Globals.TeamColors[l_user.getVariable("teamid").getIntValue()]));
						newPlayer.setTargetPos(new Vector2(l_user.getVariable("target_x").getIntValue(), l_user.getVariable("target_y").getIntValue()));
						newPlayer.getTeam().setID( l_user.getVariable("teamid").getIntValue() );
						newPlayer.enable();
					}
				}
				
				// Sync flag locations and owners.
				var numNodes:int = l_room.getVariable("numnodes").getIntValue();
				for (i = 0; i < numNodes; ++i)
				{
					var node_x:int = l_room.getVariable("node" + (i + 1) + "x").getIntValue();
					var node_y:int = l_room.getVariable("node" + (i + 1) + "y").getIntValue();
					var node_owner:int = l_room.getVariable("node" + (i + 1) + "owner").getIntValue();
					
					// Add a flag with the specified data.
					var flag:Flag = new Flag(new Vector2(node_x, node_y), i + 1);
					
					var flag_owner_team:Team = Team.getTeamByID(node_owner);
					if (flag_owner_team)
					{
						flag.attemptCapture(flag_owner_team, true);
					}
					else
					{
						flag.attemptCapture(Team.getDefaultTeam(), true);
					}
				}
			}
			
			Engine.printf("Joined room " + l_room.name);
			
			/*
			var varlist:Array = [];
			var roomVar:Object = { name:"test", value:"blahdieblah" };
			varlist.push(roomVar);
			
			m_SFS.setRoomVariables(varlist);
			
			m_SFS.getRoomById(2).getVariables*/
		}
		private function onRoomJoinError(evt:SFSEvent):void
		{
			Engine.printf("Could not join room: " + evt.params.errorMessage);
		}
		private function onRoomAdded(evt:SFSEvent):void
		{
			Engine.printf("Room " + evt.params.room.name + " added");
			
			
			if (Util.strStartsWith(evt.params.room.name, "Session"))
			{
				// Add a room variable prefixed with 'session' so that game sessions can be found by inspecting the lobby's room variables.
				var newEntry:Array = [];
				newEntry.push( new SFSRoomVariable(evt.params.room.name, evt.params.room.name) );
				MessageHandler.Get().sendSetRoomVariablesRequest(newEntry);
			}
		}
		
		private function onRoomCreationError(evt:SFSEvent):void
		{
			Engine.printf("Room " + evt.params.room.name + " creation failed");
		}
		
		private function onRoomRemove(evt:SFSEvent):void
		{			
			Engine.printf("Room " + evt.params.room.name + " removed");
			
			// Remove the room from the lobby's room variables.
			var varArray:Array = [];
			varArray.push(new SFSRoomVariable(evt.params.room.name, null));
			MessageHandler.Get().sendSetRoomVariablesRequest(varArray);
			
			// If the player is in the room, leave. (Does not work since the room is already removed by the time this point is reached).
			if (m_SFS.mySelf.isJoinedInRoom(evt.params.room) && Util.strStartsWith(evt.params.room.name, "Session"))
			{
				Engine.Get().SwitchAppState(AppState.LOBBY);
			}
			
			Engine.Get().updateSessionList();
		}
		
		private function onRoomVariablesUpdate(evt:SFSEvent):void
		{
			if (Engine.Get().getAppState() == AppState.LOBBY)
			{
				Engine.Get().updateSessionList();
			}
			else if (Engine.Get().getAppState() == AppState.GAME)
			{
				Game.Get().onRoomVariablesUpdate(evt);
			}
		}
		
		private function onExtensionResponse(evt:SFSEvent):void
		{
			var obj:SFSObject = evt.params.params as SFSObject;
			var msgID:int = obj.getInt("id");
			Engine.printf("Received extension response, cmd: " + evt.params.cmd /*+ ", id: " + msgID.toString()*/);
			
			switch (evt.params.cmd)
			{
				/*
				case ("gameslist"):
				{
					// Populate the visible list of games so that the user can select a game to join.
					
					var page:Page = Engine.Get().m_Menu.getPage(1);
					var buttons:Vector.<Button> = Engine.Get().m_Menu.getPage(1).getButtons();
					
					// Clear the games list.
					var GamesListLength:int = Engine.Get().m_GamesList.length;
					for (var i:int = 0; i < GamesListLength; ++i)
					{
						Engine.Get().m_GamesList.pop();
					}
						
					while (buttons.length > 3)
					{
						// Remove any buttons that were already added to the lobby menu.
						// The first 3 buttons should not be removed as they are not part of the game list.
						// This is nasty, yes, but it works...
						page.popButton();
					}
					Engine.printf("Gameslist is " + ((obj.getBool("empty"))?("empty"):("not empty")));	
					if (!obj.getBool("empty"))
					{
						var GameNames:Array = obj.getUtfStringArray("GameNames");
						var ConnectedPlayers:Array = obj.getIntArray("ConnectedPlayers");
						
						Engine.printf("Obtained games list (size " + GameNames.length + ")");
						
						// Verify that the data matches up.
						if (GameNames.length != ConnectedPlayers.length)
						{
							Engine.printf("Game list data does not match (size of names and size of player data)!");
						}
						
						// Now store the obtained data so that it can be presented to the player.
						for (i = 0; i < GameNames.length; ++i)
						{
							var name:String = GameNames[i];
							var numplayers:int = ConnectedPlayers[i];
							
							Engine.Get().m_GamesList.push(new GameListData(name, numplayers));
						}
						
						// Add buttons for the available games to the lobby menu.
						for (i = 0; i < GameNames.length; ++i)
						{						
							var button:Button = new Button("Game#" + i, ButtonFunctions.SetSelectedGame, null, null, null, ButtonFunctions.JoinGame, 1);
							button.setX(50);
							button.setY(150 + i * 45);
							button.setWidth(400);
							button.setHeight(38);
							button.text.text = "(" + ConnectedPlayers[i] + "/4 players) > " + GameNames[i];
							button.text.setTextFormat(Globals.Text_Format_Consolas15_AlignLeft);
							button.data = GameNames[i];
							button.rotationY = -10;
							button.text.rotationY = -10;
							
							page.addButton(button);
						}
					}
					break;
				}
				case ("newgame"):
				{
					if (obj.getBool("accepted"))
					{
						
						var gamename:String = obj.getUtfString("gamename");
						
						// The player must be now moved to this game.
						var sfsobj:SFSObject = new SFSObject();
						sfsobj.putUtfString("gamename", gamename);
						sendExtensionRequest("entersession", sfsobj);
					}
					else
					{
						trace("New session was not created");
					}
					break;
				}
				case ("entersession"):
				{
					if (obj.getBool("accepted"))
					{
						// The player has been allowed to join a game session, and has been added to that session on the server.
						Engine.Get().SwitchAppState(AppState.GAME);
						Engine.printf("Joined session");
						
						// Send user variable requests to the server. This sets the user's score and location.
						var playerData:Array = [];
						playerData.push( { name:("player_" + m_SFS.mySelf.name + "_score"), val:0  } );
						playerData.push( { name:("player_" + m_SFS.mySelf.name + "_x"), val:LocalPlayer.Get().getPos().y  } );
						playerData.push( { name:("player_" + m_SFS.mySelf.name + "_y"), val:LocalPlayer.Get().getPos().y  } );
						
						sendSetUserVariablesRequest(playerData);
					}
					else
					{
						Engine.printf("Could not join session: " + obj.getUtfString("reason"));
					}
					break;
				}
				*/
				case ("gethighscore"):
				{
					if (obj.getBool("scorefound"))
					{
						Globals.CurrentHighscore_Textfield.text = "Your current fastest time to reach " + Globals.Score_Limit + " points is: " + obj.getInt("time") + " seconds!";
						Globals.CurrentHighscore_Textfield.setTextFormat(Globals.Text_Format_Consolas20_Centered);
					}
					else
					{
						Globals.CurrentHighscore_Textfield.text = "You haven't set a fastest time to reach " + Globals.Score_Limit + " points yet.\nPlay a game against an opponent and win to set a time";
						Globals.CurrentHighscore_Textfield.setTextFormat(Globals.Text_Format_Consolas20_Centered);
					}
				}
			}
		}
		private function onUserEnterRoom(evt:SFSEvent):void
		{
			Game.Get().onUserEnterRoom(evt);
		}
		private function onUserExitRoom(evt:SFSEvent):void
		{
			Game.Get().onUserExitRoom(evt);
		}
		private function onUserVariableUpdate(evt:SFSEvent):void
		{
			Game.Get().onUserVariableUpdate(evt);
		}
		
		//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
		// Message Sending, this is done through this handler to be able to easily add things to ALL messages of a single type.
		
		public function sendExtensionRequest(name:String, params:SFSObject = null):void
		{
			if(params != null)
				// Add a message ID to the message. So the receiver can distinguish between older and newer messages.
				params.putInt("id", m_MessageID++);

			m_SFS.send( new ExtensionRequest(name, params));
		}
		public function sendLoginRequest(name:String, password:String, zoneName:String):void
		{
				var request:LoginRequest = new LoginRequest(name, password, zoneName);
				m_SFS.send( request );
		}
		public function sendJoinRoomRequest(roomName:String, pass:String = null, roomIdToLeave:Number=NaN, asSpect:Boolean=false):void
		{
			m_SFS.send(new JoinRoomRequest(roomName, pass, roomIdToLeave, asSpect));
		}
		public function sendSetUserVariablesRequest(userVariables:Array):void
		{
			m_SFS.send(new SetUserVariablesRequest(userVariables));
		}
		public function sendSetRoomVariablesRequest(a_Settings:Array, a_Room:Room = null):void
		{
			var room:Room = a_Room;
			if (room == null)
			{
				Engine.printf("WARNING: sendSetRoomVariablesRequest called with null parameter for room -- Sending variables request to lobby");
				room = MessageHandler.Get().m_SFS.getRoomByName(Globals.Lobby_Room_Name);
			}
			m_SFS.send(new SetRoomVariablesRequest(a_Settings, room));
		}
	}

}